%{
#include <stdio.h>
#include <assert.h>

#include "parser.h"
#include "ctnode.h"

int yylex ();
void yyerror (ParserState *state, char *s);
CTNode * alloc_node (CTElement type, int value, CTNode *partner);
%}

%token COMMAOP PIPEOP COLONOP PIPECOLONOP COLONPIPEOP PIPEPIPEOP OBRACEOP
       CBRACEOP COLONPIPEPIPEOP COLONPIPECOLONOP OBRACKETOP CBRACKETOP
%token BPMKW BEATSKW AVOLKW QVOLKW EVOLKW SVOLKW TVOLKW ACCELKW DECELKW
%token NUMBER

%start track

%union {
  CTElement ctelemval;
  int intval;
}

%type<intval> NUMBER
%type<ctelemval> option
%type<intval> repeat_count

%parse-param { ParserState *state }

%%

measure_separator: PIPEOP
                   ;
repeat_count: /* Empty */
              { $$ = 1; }
              | OBRACKETOP NUMBER CBRACKETOP
              { $$ = $2; }
              ;
track_end: PIPEPIPEOP
           | COLONPIPEPIPEOP repeat_count
           {
             CTNode **partner = stack_top (&state->repeat_stack);
             CTNode *node;
             if (partner == NULL) {
               node = alloc_node (RepeatBeginElement, 0, NULL);
               ctlist_prepend_node (&state->track, node);
               stack_push (&state->repeat_stack, &node);
               partner = &node;
             }
             stack_pop (&state->repeat_stack);
             node = alloc_node (RepeatEndElement, $2, *partner);
             ctlist_append_node (&state->track, node);
           }
           ;
repeat_begin: PIPECOLONOP
              {
                CTNode *node = alloc_node (RepeatBeginElement, 0, NULL);
                ctlist_append_node (&state->track, node);
                stack_push (&state->repeat_stack, &node);
              }
              ;
repeat_end: COLONPIPEOP repeat_count
            {
              CTNode **partner = stack_top (&state->repeat_stack);
              CTNode *node;
              if (partner == NULL) {
                node = alloc_node (RepeatBeginElement, 0, NULL);
                ctlist_prepend_node (&state->track, node);
                stack_push (&state->repeat_stack, &node);
                partner = &node;
              }
              stack_pop (&state->repeat_stack);
              node = alloc_node (RepeatEndElement, $2, *partner);
              ctlist_append_node (&state->track, node);
            }
            ;
repeat_end_begin: COLONPIPECOLONOP repeat_count
                  {
                    CTNode **partner = stack_top (&state->repeat_stack);
                    CTNode *node;
                    if (partner == NULL) {
                      node = alloc_node (RepeatBeginElement, 0, NULL);
                      ctlist_prepend_node (&state->track, node);
                      stack_push (&state->repeat_stack, &node);
                      partner = &node;
                    }
                    stack_pop (&state->repeat_stack);
                    node = alloc_node (RepeatEndElement, $2, *partner);
                    ctlist_append_node (&state->track, node);

                    node = alloc_node (RepeatBeginElement, 0, NULL);
                    ctlist_append_node (&state->track, node);
                    stack_push (&state->repeat_stack, &node);
                  }
                  ;

separator: measure_separator
           | repeat_begin
           | repeat_end
           | repeat_end_begin
           ;
option: BPMKW
        { $$ = TempoElement; }
        | BEATSKW
        { $$ = BeatsElement; }
        | AVOLKW
        { $$ = AccentLevelElement; }
        | QVOLKW
        { $$ = QuarterLevelElement; }
        | EVOLKW
        { $$ = EighthLevelElement; }
        | SVOLKW
        { $$ = SixteenthLevelElement; }
        | TVOLKW
        { $$ = TripletLevelElement; }
        | ACCELKW
        { $$ = AccelElement; }
        | DECELKW
        { $$ = DecelElement; }
        ;

param_list: param param_tail
            ;
param: option COLONOP NUMBER
       {
         CTNode *node = alloc_node ($1, $3, NULL);
         ctlist_append_node (&state->track, node);
       }
       ;
param_tail: /* Empty */
            | COMMAOP param param_tail
            ;

measure_list: measure measure_tail
              ;
measure: OBRACEOP param_list CBRACEOP NUMBER
         {
           CTNode *node = alloc_node (MeasureElement, $4, NULL);
           ctlist_append_node (&state->track, node);
         }
         ;
measure_tail: /* Empty */
              | separator measure measure_tail
              ;

track: measure_list track_end
       ;

%%

void yyerror (ParserState *state, char *s)
{
  printf ("Error parsing click track: %s\n", s);
}

CTNode * alloc_node (CTElement type, int value, CTNode *partner)
{
  CTNode *node = malloc (sizeof (CTNode));
  assert (node);
  node->type = type;
  node->value = value;
  node->partner = partner;
  return node;
}
